#include "SudokuData.h"
#include "SudokuSolver.h"
#include "math.h"

#include <QStringList>
#include <QRegExp>
#include <QFile>
//#include <windows.h>//TODO linux

SudokuData::SudokuData(int _sizeBox) : QObject()
{
    sizeBox = _sizeBox;
    size = sizeBox * sizeBox;

    for(int i=0;i<size;++i)
        for(int j=0;j<size;++j)
            cells.append(new SudokuCell(sizeBox, j, i));//TODO col, row

    generateHouses();
}

SudokuData::SudokuData(QString sudokuNumbers) : QObject()
{
    construct(sudokuNumbers);
}

SudokuData::SudokuData(QString filePath, QString fileName) : QObject()
{
    QByteArray array = readFromFile(filePath, fileName);
    QString sudokuNumbers(array);

    construct(sudokuNumbers);
}

SudokuData::~SudokuData()
{
    qDebug() << ">>SudokuData::~SudokuData";
    for(int i=0;i<cells.count();++i)
        delete cells[i];
    qDebug() << "<<SudokuData::~SudokuData";
}

void SudokuData::construct(QString sudokuNumbers)
{
    //TODO XML format
    sudokuNumbers.replace('.','0');
    sudokuNumbers.replace(QChar('x'),QChar('0'),Qt::CaseInsensitive);
    sudokuNumbers = sudokuNumbers.trimmed();
    sudokuNumbers = sudokuNumbers.simplified();
    sudokuNumbers = sudokuNumbers.remove(" ");
    sudokuNumbers = sudokuNumbers.remove("-");//*.ss format support
    sudokuNumbers = sudokuNumbers.remove("|");//*.ss format support
    qDebug() << "SudokuData::construct: " << sudokuNumbers;
    int indexOfFirstNotNumber = sudokuNumbers.indexOf(QRegExp("[^0123456789]"));

    qDebug() << "SudokuData::construct sudokuNumbers.size():" << sudokuNumbers.size();
    QStringList numberList;

    qDebug() << "SudokuData::construct indexOfFirstNotNumber" << indexOfFirstNotNumber;
    if(indexOfFirstNotNumber == -1 || indexOfFirstNotNumber > 10)//TODO
    {
        qDebug() << "SudokuData::construct plain format without delimiter";
        for(int i=0;i<sudokuNumbers.size();++i)
        {
            //qDebug() << "SudokuData::construct " << sudokuNumbers.at(i);
            numberList.append(sudokuNumbers.at(i));
        }
    }
    else
    {
        numberList = sudokuNumbers.split(QRegExp("[^0123456789]"), QString::SkipEmptyParts);
    }

    double boxSize = floor( sqrt(sqrt( static_cast<double>(numberList.size()) )) );
    qDebug() << "SudokuData::construct boxSize: " << boxSize;

    sizeBox = static_cast<int>(boxSize);

    if(sizeBox < 2)
        qFatal("SudokuData::construct minimum size is 2, not enought data");

    size = sizeBox * sizeBox;

    if(size * size < numberList.size())
        qWarning("SudokuData::construct wrong data size, not all numbers will be used");

    for(int i=0;i<numberList.size();++i)
    {
        bool ok;
        int cellValue = numberList[i].toInt(&ok);
        if(!ok)
            qFatal("SudokuData::construct conversion error");//TODO throw?

        if(cellValue < 0 || cellValue > size)
            qFatal("SudokuData::construct wrong number");//TODO throw?

        //TODO change cells to pointers, copy constructor works here!!!
        if(cellValue == 0)
            cells.append(new SudokuCell(sizeBox, i%size, i/size));//TODO row,col
        else
            cells.append(new SudokuCell(sizeBox, cellValue, Given, i%size, i/size));
    }

    generateHouses();

    excludeAllPossibl();
}

void SudokuData::generateHouses()
{
    for(int i=0;i<size;++i)
    {
        rows.append(SudokuHouse(SudokuCellSet::Row, this));
        cols.append(SudokuHouse(SudokuCellSet::Col, this));
        boxes.append(SudokuHouse(SudokuCellSet::Box, this));
    }

    qDebug() << "SudokuData::generateHouses 1";
    for(int i=0;i<size*size;++i)
    {
        SudokuCell* cell = cells[i];
        rows[cell->getRow()].append(cell);
        cols[cell->getCol()].append(cell);
        boxes[cell->getBoxIndex()].append(cell);
    }
    qDebug() << "SudokuData::generateHouses 2";

    for(int i=0;i<size;++i)
    {
        qDebug() << "row count: " << rows[i].size()
                 << "col count: " << cols[i].size()
                 << "box count: " << boxes[i].size();
    }
}

void SudokuData::excludeAllPossibl()
{
    for(int cellInd=0;cellInd<size*size;++cellInd)
        if(!cells[cellInd]->isEmpty())
            excludePossibl(cells[cellInd]);
}

void SudokuData::excludePossibl(SudokuCell* cell)
{
    // -1 because setPossibl is indexed from 0

    int cellValueDecr = cell->getValue() - 1;
    SudokuCell* currentCell = 0;
    for(int cellInd=0;cellInd<size;++cellInd)
    {
        currentCell = cols[cell->getCol()].at(cellInd);
        if(currentCell->isEmpty() && currentCell->getPossibl(cellValueDecr))
        {
            currentCell->setPossibl(cellValueDecr, false, Elimination);
            emit possiblExcluded(currentCell->getRow(), currentCell->getCol(), cellValueDecr, Elimination);
        }

        currentCell = rows[cell->getRow()].at(cellInd);
        if(currentCell->isEmpty() && currentCell->getPossibl(cellValueDecr))
        {
            currentCell->setPossibl(cellValueDecr, false, Elimination);
            emit possiblExcluded(currentCell->getRow(), currentCell->getCol(), cellValueDecr, Elimination);
        }

        currentCell = boxes[cell->getBoxIndex()].at(cellInd);
        if(currentCell->isEmpty() && currentCell->getPossibl(cellValueDecr))
        {
            currentCell->setPossibl(cellValueDecr, false, Elimination);
            emit possiblExcluded(currentCell->getRow(), currentCell->getCol(), cellValueDecr, Elimination);
        }
    }
}

bool SudokuData::setCellValue(int row, int col, int value, CellFillerTypes type)
{
    qDebug() << "SudokuData::setCellValue r=" << row << " col=" << col << " type=" << type;
    // Check if value and type is already set
    if(cellAt(row, col)->getValue() == value && cellAt(row, col)->getPrior() == type)
        return false;

    //If cell is set to empty
    if(0 == value)
    {
        cellAt(row, col)->setValue(value, type);
        regenerateCandidates();
        return true;
    }
    //TODO inform user when value is rejected
    else if(value > 0 && value <= size && cellAt(row, col)->getPossibl(value-1))
    {
        bool isEmptyCell = cellAt(row, col)->isEmpty();
        cellAt(row, col)->setValue(value, type);


        if(isEmptyCell)
        {
            // If cell is empty
            excludePossibl(cellAt(row, col));
        }
        else
        {
            // If cell is not empty and value is changed
            regenerateCandidates();
        }

        //TODO check if still solvable

        //if(!isSolvable())//TODO only for tests, remove it!
        //    qCritical("no solution!!!!!!!!!!!!!!");
        return true;
    }
    return false;
}

void SudokuData::regenerateCandidates()
{
    for(int cellInd=0;cellInd<size*size;++cellInd)
        if(cells[cellInd]->isEmpty())
            cells[cellInd]->resetEliminated();

    excludeAllPossibl();
}

void SudokuData::onFoundValue(int row, int col, int value, CellFillerTypes method)
{
    qDebug() << "SudokuData::onFoundValue row" << row << "col" << col << "value" << value << "method" << method;
    if(cells[row*size + col]->getPossibl(value-1) == 0)//TODO only for tests, remove it!
    {
        //sleep(2);//not working on windows
        qFatal("Bad number found");
    }
    cells[row*size + col]->setValue(value,method);
    excludePossibl(cells[row*size + col]);
    if(!isSolvable())//TODO only for tests, remove it!
        qCritical("no solution!!!!!!!!!!!!!!");
    emit valueFound(row, col, value, method);
    //sleep(1);//TODO !!!
    //Sleep(1000);
}

//TODO change name
void SudokuData::onExcludePossibl(int index, int number, CellFillerTypes method)
{
    Q_ASSERT(index < cells.size());
    SudokuCell* cell = cells[index];
    if(cell->getPossibl(number))
    {
        cell->setPossibl(number, false, method);
        emit possiblExcluded(cell->getRow(), cell->getCol(), number, method);
    }
    else
    {
        qDebug() << "SudokuData::onExcludePossibl not needed";
    }
}

int SudokuData::getCellCount(CellFillerTypes cellType) const
{
    int count = 0;

    for(int i=0;i<cells.size();++i)
        if(cells[i]->getPrior() & cellType)
            ++count;

    return count;
}

int SudokuData::getEliminatedCandidatesCount(CellFillerTypes method) const
{
    int count = 0;

    for(int i=0;i<cells.size();++i)
        count += cells[i]->getEliminateCount(method);

    return count;
}

void SudokuData::clear()
{
    for(int i=0;i<cells.size();++i)
        if(!cells[i]->isGiven())
            cells[i]->clear();
    excludeAllPossibl();
}

double SudokuData::countPossibleSolutions() const
{
    double count = 1;
    QString mulStr("");
    for(int i=0;i<cells.size();++i)
        if(cells[i]->getValue() == 0)
        {
            //qDebug() << "cell possibl: " << cells[i].possiblArr.count(true);
            mulStr += QString::number(cells[i]->countPossible()) + "*";
            count *= cells[i]->countPossible();
            //qDebug() << "count: " << count;
        }
    qDebug() << mulStr << " = " << count;
    return count;

}

int SudokuData::countIndividuals() const
{
    int indivCount = 0;
    QBitArray countRow(size);
    QBitArray countCol(size);
    QBitArray countBox(size);

    for(int i=0;i<size;++i)
    {
        countRow.fill(false);
        countCol.fill(false);
        countBox.fill(false);

        for(int j=0;j<size;++j)
        {
            countRow.setBit(rows[i].at(j)->getValue() - 1, true);
            countCol.setBit(cols[i].at(j)->getValue() - 1, true);
            countBox.setBit(boxes[i].at(j)->getValue() - 1, true);
        }

        //qDebug() << "row: " << countRow.count(true) << "col: " << countCol.count(true) << "box: " << countBox.count(true);
        indivCount += countRow.count(true);
        indivCount += countCol.count(true);
        indivCount += countBox.count(true);
    }
    qDebug() << "indivCount " << indivCount;
    return indivCount;
}

bool SudokuData::isSolvable() const
{
    return checkDiagram();
}

//sprawdza czy diagram jest wypelniony liczbami z prior>1, uzywane do spr. czy rozwiazane przed uruchomieniem BF
bool SudokuData::isFilled() const
{
    for(int i=0;i<size*size;++i)
        if(cells[i]->isEmpty())
            return false;
    return true;
}

bool SudokuData::isSolved() const
{
    if(!isFilled())
        return false;
    if(countIndividuals() < size * size * 3)
        return false;
    return true;
}

bool SudokuData::isValid() const
{
    if(isSolved())
        return true;

    if( !isFilled() && isSolvable())
        return true;

    return false;
}

//TODO move to house
bool SudokuData::checkHouse(const SudokuHouse& house) const
{
    QVector<int> insertedValues;
    insertedValues.fill(0,house.size());
    QVector<int> possibleValues;
    possibleValues.fill(0,house.size());

    for(int i=0; i<house.size(); ++i)
    {
        if(house.at(i)->isEmpty())
        {
            for(int candidate=0 ;candidate<house.at(i)->getPossiblArrSize(); ++candidate)
            {
                possibleValues[candidate] += house.at(i)->getPossibl(candidate);
            }
        }
        else
        {
            insertedValues[house.at(i)->getValue()-1] += 1;
        }
    }

    /*
    qDebug() << "insertedValues: " << insertedValues;
    qDebug() << "possibleValues: " << possibleValues;*/

    for(int ind=0;ind<house.size(); ++ind)
    {
        if(insertedValues.at(ind)>1)//2 numbers inserted
        {
            qDebug() << "SudokuData::checkVector 2 numbers inserted " << ind;
            return false;
        }

        if(insertedValues.at(ind) == 0)//vector doesn't contain number
        {
            if(possibleValues.at(ind)<1)//if there is no cell to put that number
            {
                qDebug() << "SudokuData::checkVector no cell to put number " << ind;
                return false;
            }
        }
    }

    return true;
}

bool SudokuData::checkDiagram() const
{
    for(int i=0;i<size;++i)//cells in row/col/box from 1 to size
    {
        if(!checkHouse(rows[i]))
        {
            qDebug() << "SudokuData::checkDiagram row " << i;
            return false;
        }

        if(!checkHouse(cols[i]))
        {
            qDebug() << "SudokuData::checkDiagram col " << i;
            return false;
        }

        if(!checkHouse(boxes[i]))
        {
            qDebug() << "SudokuData::checkDiagram box " << i;
            return false;
        }
    }

    //qDebug() << "SudokuData::checkDiagram";
    return true;
}

bool SudokuData::saveToFile(QString filePath, QString fileName)
{
    qFatal("not implemented");
    QFile* file = new QFile (filePath + fileName);

    if ( !file->open(QIODevice::WriteOnly) )
        return false;

    //file->write ( ... );//TODO save as XML with prior
    file->close ();

    return true;
}

QByteArray SudokuData::readFromFile(QString filePath, QString fileName)
{
    QFile* file = new QFile (filePath + fileName);

    QByteArray array;
    if (file->open(QIODevice::ReadOnly))
    {
        array = file->readAll();
        file->close ();
    }

    delete file;

    return array;
}

QDebug operator<<(QDebug dbg, const SudokuData& sud)
{
    for(int i=0;i<sud.size;++i)
    {
        for(int j=0;j<sud.size;++j)
        {
            dbg.nospace() << *sud.cells[i*sud.size + j] << '.';
        }
        dbg.nospace() << endl;
    }

    return dbg.maybeSpace();
}
